using Cil.Ast.Cil;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace Cil.Ast;

public sealed class CastValueExpression : CastingExpression
{
	public const int InstancePos = 0;
	public const int MaxNodes = 1;

	public CastValueExpression(Context context, Instruction position, Expression instance)
		: base(context, position)
	{
		TypeReference castTo;

		CheckPrimitive(instance.ReturnType);

		switch(position.OpCode.Code)
		{
			case Code.Conv_I:
			case Code.Conv_Ovf_I:
			case Code.Conv_Ovf_I_Un:
				castTo = Context.MakeRef<IntPtr>();
				break;

			case Code.Conv_I1:
			case Code.Conv_Ovf_I1:
			case Code.Conv_Ovf_I1_Un:
				castTo = Context.MakeRef<sbyte>();
				break;

			case Code.Conv_I2:
			case Code.Conv_Ovf_I2:
			case Code.Conv_Ovf_I2_Un:
				castTo = Context.MakeRef<short>();
				break;

			case Code.Conv_I4:
			case Code.Conv_Ovf_I4:
			case Code.Conv_Ovf_I4_Un:
				castTo = Context.MakeRef<int>();
				break;

			case Code.Conv_I8:
			case Code.Conv_Ovf_I8:
			case Code.Conv_Ovf_I8_Un:
				castTo = Context.MakeRef<long>();
				break;

			case Code.Conv_U:
			case Code.Conv_Ovf_U:
			case Code.Conv_Ovf_U_Un:
				castTo = Context.MakeRef<UIntPtr>();
				break;

			case Code.Conv_U1:
			case Code.Conv_Ovf_U1:
			case Code.Conv_Ovf_U1_Un:
				castTo = Context.MakeRef<byte>();
				break;

			case Code.Conv_U2:
			case Code.Conv_Ovf_U2:
			case Code.Conv_Ovf_U2_Un:
				castTo = Context.MakeRef<ushort>();
				break;

			case Code.Conv_U4:
			case Code.Conv_Ovf_U4:
			case Code.Conv_Ovf_U4_Un:
				castTo = Context.MakeRef<uint>();
				break;

			case Code.Conv_U8:
			case Code.Conv_Ovf_U8:
			case Code.Conv_Ovf_U8_Un:
				castTo = Context.MakeRef<ulong>();
				break;

			case Code.Conv_R_Un:
				castTo = Context.MakeRef<float>();
				break;

			case Code.Conv_R4:
				castTo = Context.MakeRef<float>();
				break;

			case Code.Conv_R8:
				castTo = Context.MakeRef<double>();
				break;

			default:
				throw new ArgumentException(
					$"Instruction operand have unsupported type to cast to: {position.OpCode.Code} opcode code");
		}

		if(instance.WritesStack != 1)
		{
			throw new InvalidOperationException(
				$"Wrong stack items count in <exp_length> argument: {instance.WritesStack}");
		}

		if(!Cast.IsAvaliable(instance.ReturnType, castTo))
		{
			throw new InvalidCastException(
				$"<exp_instance> ({instance}) type cannot be initialized as <ref_type> ({castTo})");
		}

		// base class
		ReadsStack = 1;
		WritesStack = 1;
		Nodes = new FixedList<Expression>(MaxNodes);
		ReturnType = castTo;

		// this
		InstanceExpression = instance;
		CastTo = castTo;
	}

	private void CheckPrimitive(TypeReference returnType)
	{
		if(!returnType.IsPrimitive)
		{
			throw new ArgumentException($"type should be primitive ({returnType})");
		}
	}

	private void CheckType<TType>(TypeReference returnType)
	{
		if(!returnType.HardEquals(Context.MakeRef<TType>()))
		{
			throw new ArgumentException($"type casting from {returnType} to {typeof(TType)} is wrong)");
		}
	}

	public Expression InstanceExpression  { get { return Nodes[InstancePos]; } set { Nodes[InstancePos] = value;  value.ParentNode = this; } }

	public override string ToString()
	{
		return string.Format("({1})({0})", InstanceExpression, ReturnType);
	}
}

public static partial class IlAst
{
	public static CastValueExpression CastValue(Context context, Instruction position, Expression instance)
	{
		return new CastValueExpression(context, position, instance);
	}
}